/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.cst;

import com.android.dx.util.ExceptionWithContext;
import com.android.dx.util.Hex;
import com.android.dx.util.MutabilityControl;

/**
 * Standard implementation of {@link ConstantPool}, which directly stores an
 * array of {@link Constant} objects and can be made immutable.
 */
public final class StdConstantPool extends MutabilityControl implements
		ConstantPool {

	/** {@code non-null;} array of entries */
	private final Constant[] entries;

	/**
	 * Constructs an instance. All indices initially contain {@code null}.
	 * 
	 * @param size
	 *            the size of the pool; this corresponds to the class file field
	 *            {@code constant_pool_count}, and is in fact always at least
	 *            one more than the actual size of the constant pool, as element
	 *            {@code 0} is always invalid.
	 */
	public StdConstantPool(int size) {
		super(size > 1);

		if (size < 1) {
			throw new IllegalArgumentException("size < 1");
		}

		entries = new Constant[size];
	}

	/** {@inheritDoc} */
	public int size() {
		return entries.length;
	}

	/** {@inheritDoc} */
	public Constant getOrNull(int n) {
		try {
			return entries[n];
		} catch (IndexOutOfBoundsException ex) {
			// Translate the exception.
			return throwInvalid(n);
		}
	}

	/** {@inheritDoc} */
	public Constant get0Ok(int n) {
		if (n == 0) {
			return null;
		}

		return get(n);
	}

	/** {@inheritDoc} */
	public Constant get(int n) {
		try {
			Constant result = entries[n];

			if (result == null) {
				throwInvalid(n);
			}

			return result;
		} catch (IndexOutOfBoundsException ex) {
			// Translate the exception.
			return throwInvalid(n);
		}
	}

	/**
	 * Sets the entry at the given index.
	 * 
	 * @param n
	 *            {@code >= 1, < size();} which entry
	 * @param cst
	 *            {@code null-ok;} the constant to store
	 */
	public void set(int n, Constant cst) {
		throwIfImmutable();

		boolean cat2 = (cst != null) && cst.isCategory2();

		if (n < 1) {
			throw new IllegalArgumentException("n < 1");
		}

		if (cat2) {
			// Storing a category-2 entry nulls out the next index.
			if (n == (entries.length - 1)) {
				throw new IllegalArgumentException("(n == size - 1) && "
						+ "cst.isCategory2()");
			}
			entries[n + 1] = null;
		}

		if ((cst != null) && (entries[n] == null)) {
			/*
			 * Overwriting the second half of a category-2 entry nulls out the
			 * first half.
			 */
			Constant prev = entries[n - 1];
			if ((prev != null) && prev.isCategory2()) {
				entries[n - 1] = null;
			}
		}

		entries[n] = cst;
	}

	/**
	 * Throws the right exception for an invalid cpi.
	 * 
	 * @param idx
	 *            the bad cpi
	 * @return never
	 * @throws ExceptionWithContext
	 *             always thrown
	 */
	private static Constant throwInvalid(int idx) {
		throw new ExceptionWithContext("invalid constant pool index "
				+ Hex.u2(idx));
	}
}
